之前我們提到任務的 created_at 與 updated_at 兩個欄位因為產生欄位值的時間點有一點點誤差,導致兩個時間不一致的問題。今天讓我們用客製化欄位的方式來解決這個問題吧!
讓我們先建立 server/utils/models.py
檔案
from django.db import models
class CreatedAtField(models.DateTimeField):
def __init__(self, *args, **kwargs) -> None:
kwargs["auto_now_add"] = True
super().__init__(*args, **kwargs)
這邊做的事情是建立了一個客製化欄位名為 CreatedAtField 他繼承了 DateTimeField,唯一的不同是他會自動地將 auto_now_add
設定為 True 這樣當使用這個欄位時就不用手動修改了,現在讓我們打開 server/app/todo/models.py
檔案
from django.db import models
+from server.utils import models as model_utils
+
class Tag(models.Model):
name = models.CharField(max_length=255, unique=True)
description = models.TextField(blank=True)
def __str__(self):
return self.name
# ...... 中間省略 ......
class Task(models.Model):
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
is_finish = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag)
end_at = models.DateTimeField(null=True, blank=True)
- created_at = models.DateTimeField(auto_now_add=True)
+ created_at = model_utils.CreatedAtField()
updated_at = models.DateTimeField(auto_now=True)
category = models.ForeignKey(Category, on_delete=models.PROTECT)
def __str__(self):
return self.title
這邊我們將任務的 created_at 欄位替換成我們剛剛建立的欄位,接著讓我們產生一下遷移檔,並套用到資料庫(記得啟動虛擬環境)
python manage.py makemigrations
python manage.py migrate
現在大家可以使用 POST 方法請求 http://127.0.0.1:8000/api/todo/tasks(記得啟動 server 唷)
大家可以發現效果還是跟之前一樣,這是正常的因為我們目前還沒有調整最後編輯時間。
接著我們編輯 server/utils/models.py
檔案(把下方內容貼在最下面)
class UpdatedAtField(models.DateTimeField):
def __init__(self, *args, **kwargs) -> None:
kwargs["auto_now"] = True
super().__init__(*args, **kwargs)
def pre_save(self, model_instance, add):
if add:
for field in model_instance._meta.get_fields():
if not isinstance(field, CreatedAtField):
continue
value = getattr(model_instance, field.name)
setattr(model_instance, self.attname, value)
return value
return super().pre_save(model_instance, add)
這邊我們做的事情是建立一個 UpdatedAtField 他繼承了 DateTimeField,只是他會自動地將 auto_now
設定為 True,同時他做資料儲存時他會先判斷目前是建立還是更新(透過 pre_save 的 add 參數),如果是更新或是 Model 中沒有 CreatedAtField 的話呼叫 DateTimeField 的 pre_save,但如果是新增且有 CreatedAtField 的話,就使用 CreatedAtField 產生的時間當作當前欄位的值,這樣 created_at 與 updated_at 的時間就會相同了。
現在讓我們編輯 server/app/todo/models.py
檔案
# ...... 以上省略 ......
class Task(models.Model):
title = models.CharField(max_length=255)
description = models.TextField(blank=True)
is_finish = models.BooleanField(default=False)
tags = models.ManyToManyField(Tag)
end_at = models.DateTimeField(null=True, blank=True)
created_at = model_utils.CreatedAtField()
- updated_at = models.DateTimeField(auto_now=True)
+ updated_at = model_utils.UpdatedAtField()
category = models.ForeignKey(Category, on_delete=models.PROTECT)
def __str__(self):
return self.title
這邊我們將任務的 updated_at 欄位替換成我們剛剛建立的欄位,接著讓我們產生一下遷移檔,並套用到資料庫(記得啟動虛擬環境)
python manage.py makemigrations
python manage.py migrate
現在大家可以使用 POST 方法請求 http://127.0.0.1:8000/api/todo/tasks 可以發現 created_at 與 updated_at 兩個欄位的值是一樣的了,但是在編輯時還是只會更新 updated_at 的值。
今天這樣修改後,我們就可以讓 created_at 與 updated_at 這兩個欄位的值在建立資料時是一樣的。如果大家有需要重複利用的欄位邏輯,或是希望基於某個欄位修改他的行為,就可以跟我們今天一樣繼承原本的欄位來修改,如果想知道怎麼修改可以看看這篇文件。
結束前別忘了檢查一下今天的程式碼有沒有問題,並排版好喔。
ruff check --fix .
black .
pyright .
今天的內容就到這邊了,讓我們期待明天的內容吧。
P.S. 今天的檔案更新可以參考我的 Git Commit 大家可以搭配服用